package cn.practice.chat.handler;

import cn.practice.chat.config.ChannelGroupConfig;
import cn.practice.chat.enums.ChatTypeEnum;
import cn.practice.chat.lang.Result;
import cn.practice.chat.message.MqMessage;
import cn.practice.chat.response.chat.GroupUserResponse;
import cn.practice.chat.response.message.MessageRecord;
import cn.practice.chat.stream.MessageProducer;
import cn.practice.chat.user.feign.UserApiFeign;
import cn.practice.chat.utils.JwtUtils;
import cn.practice.chat.utils.SpringContextUtil;
import com.alibaba.fastjson.JSONObject;
import io.jsonwebtoken.Claims;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import static cn.practice.chat.handler.ChannelMap.CHANNEL_MAP;
import static cn.practice.chat.stream.TopicConst.what_topic;

/**
 * @ClassName WebSocketHandler
 * @Description:
 * @Author A-TAO
 * @Date 2022/10/30
 * @Version V1.0
 **/
@Slf4j
public class WebSocketHandler extends SimpleChannelInboundHandler<Object> {



    private WebSocketServerHandshaker webSocketServerHandshaker;


    JwtUtils jwtUtils;


    UserApiFeign userApiFeign;


    /**
     * 当客户段连接上服务端
     **/
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        log.info("handlerAdded, 有新的客户段链接进来了,连接channel={}, ChannelId={}", ctx.channel(), ctx.channel().id());
        ChannelGroupConfig.channelGroup.add(ctx.channel());
    }


    /**
     * 每当服务端断开客户端连接,客户端的channel从ChannelGroup中移除,并通知列表中其他客户端channel
     **/
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        log.info("handlerRemoved, 服务端断开客户端连接,断开连接channel={}, ChannelId={}", ctx.channel(), ctx.channel().id());
        //从服务端的channelGroup中移除当前离开的客户端
        ChannelGroupConfig.channelGroup.remove(channel);
        // 移除channelUserMap中的数据
        String userCode = "";

        for(String key: CHANNEL_MAP.keySet()){
            Channel userChannel = CHANNEL_MAP.get(key);
            if(userChannel.equals(channel)){
                userCode = key;
                break;
            }
        }
        // 移除
        Collection<Channel> channels = CHANNEL_MAP.values();
        while (true == channels.contains(channel)){
            channels.remove(ctx.channel());
            log.info("handlerRemoved,netty客户端连接删除成功!,删除channel:{},channelId:{}", ctx.channel(), ctx.channel().id());
        }

    }

    /**
     *  服务端与客户端连接成功
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        log.info("channelActive,netty客户端连接成功，通道开启！channel:{},channelId:{}", ctx.channel(), ctx.channel().id());
    }


    /**
     * 服务端与客户段断开连接
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        log.info("channelActive,netty与客户端断开连接，通道关闭！channel:{},channelId:{}", ctx.channel(), ctx.channel().id());
    }

    /**
     * 异常
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext context, Throwable throwable) throws Exception {
        log.info("exceptionCaught,抛出异常，异常信息{}，异常信息channel:{},channelId:{}",throwable.getLocalizedMessage(),context.channel(),context.channel().id());
        handlerRemoved(context);
        context.close();
        throwable.toString();
    }

    /**
     * 处理客户端的消息
     */
    @Override
    protected void channelRead0(ChannelHandlerContext context, Object o) throws Exception {
        if(o instanceof FullHttpRequest){
            log.info("http连接请求。。。。");
            handHttpRequest(context, (FullHttpRequest) o );
        } else if(o instanceof WebSocketFrame){
            log.info("WebSocket请求。。。。");
            handlerWebSocketFrame(context, (WebSocketFrame) o);
        }
    }


    /**
     * 处理客户端向服务端发起http握手请求业务
     * @param context
     * @param fullHttpRequest
     */
    private void handHttpRequest(ChannelHandlerContext context,FullHttpRequest fullHttpRequest){
        log.info("请求连接的channel{}，id为{}",context.channel(),context.channel().id());
        //判断是否http握手请求
        if (!fullHttpRequest.decoderResult().isSuccess()
                ||!("websocket".equals(fullHttpRequest.headers().get("Upgrade")))){
            sendHttpResponse(context,fullHttpRequest, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
            return;
        }
        HttpHeaders httpHeaders = fullHttpRequest.headers();
        String host = httpHeaders.get("host");
        String webSocketUrl = "ws://"+host+"/ws";
        WebSocketServerHandshakerFactory webSocketServerHandshakerFactory = new WebSocketServerHandshakerFactory(webSocketUrl,null,false);
        webSocketServerHandshaker = webSocketServerHandshakerFactory.newHandshaker(fullHttpRequest);
        if (webSocketServerHandshaker == null){
            WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(context.channel());
        }else{
            webSocketServerHandshaker.handshake(context.channel(),fullHttpRequest);
        }
        //把token解析成用户Code
        Channel channel = context.channel();
        String uri = fullHttpRequest.uri();

        String token = uri.substring(uri.lastIndexOf("?")+1,uri.length());

        // 解析token 获取用户name
        jwtUtils = SpringContextUtil.getBean(JwtUtils.class);
        Claims claimByToken = jwtUtils.getClaimByToken(token);
        String username = claimByToken.getSubject();
        log.info("连接netty的客户端是：{}", username);
        CHANNEL_MAP.put(username, channel);
        log.info("channelUserMap = {}", JSONObject.toJSONString(CHANNEL_MAP));

        //sendFriendMsgLoginOrOut(userCode,"notice","上线了");

    }

    /**
     * 服务端想客户端发送响应消息
     * @param context
     * @param fullHttpRequest
     * @param defaultFullHttpResponse
     */
    private void sendHttpResponse(ChannelHandlerContext context, FullHttpRequest fullHttpRequest, DefaultFullHttpResponse defaultFullHttpResponse){
        if (defaultFullHttpResponse.getStatus().code() != 200){
            ByteBuf buf = Unpooled.copiedBuffer(defaultFullHttpResponse.getStatus().toString(), CharsetUtil.UTF_8);
            defaultFullHttpResponse.content().writeBytes(buf);
            buf.release();
        }
        //服务端向客户端发送数据
        ChannelFuture future = context.channel().writeAndFlush(defaultFullHttpResponse);
        if (defaultFullHttpResponse.getStatus().code() !=200){
            future.addListener(ChannelFutureListener.CLOSE);
        }

    }


    /**
     * @description 处理websockt消息
     * @param
     * @return
     **/
    private void handlerWebSocketFrame(ChannelHandlerContext context, WebSocketFrame webSocketFrame){
        // 判断是不是关闭websocket指令
        if(webSocketFrame instanceof CloseWebSocketFrame){
            webSocketServerHandshaker.close(context.channel(), ((CloseWebSocketFrame) webSocketFrame).retain());
            return;
        }
        // 判断是不是ping消息
        if(webSocketFrame instanceof PingWebSocketFrame){
            context.channel().write(new PongWebSocketFrame(webSocketFrame.content().retain()));
            return;
        }
        if(!(webSocketFrame instanceof TextWebSocketFrame)){
            log.error("不支持二进制消息");
            throw new RuntimeException(this.getClass().getName());
        }

        String content = ((TextWebSocketFrame) webSocketFrame).text();
        log.info("服务端收到客户端消息：{}", content);
        MessageRecord record = JSONObject.parseObject(content, MessageRecord.class);

        // 不管用户在不在线都保存数据
        userApiFeign = SpringContextUtil.getBean(UserApiFeign.class);
        userApiFeign.addRecord(record);
        MessageProducer messageProducer = SpringContextUtil.getBean(MessageProducer.class);
        MqMessage mqMessage = new MqMessage();
        mqMessage.setRecord(record);
        mqMessage.setChannel(context.channel());
        messageProducer.sendMessage(what_topic, mqMessage);
    }
}
